﻿using System;
using System.Web;
using System.Web.Mvc;
using System.IO;
using Liber;
using Liber.WEB;

namespace SoBusiness.Controllers
{
    public class FileController : LiberController
    {
        public void DebugToken()
        {
            API.Request("FileSvc@CheckToken").WaitResult();
        }

        public JsonResult FileAPI(string mode, string fileName, string token, int start = 0, int total = 0,string rootFolder=@"/UserFile")
        {
            switch (mode.ToLower())
            {
                case "upload":
                    return upload(fileName, token);
                case "getinfo":
                    return getInfo(fileName, token);
                case "append":
                    return append(fileName, token, start, total);
                case "fromwx":
                    return getFromWx(fileName, fileName.SHA1(),rootFolder);
                default:
                    FnResult r = FnResult.Failure("SoBiz", "FileAPIError").SetError("action", "unrecorgnize");
                    return Json(r);
            }
        }

        //public void Debug()
        //{
        //    //将转换的pdf文档再上传到标准上传接口
        //    string nonce = new Random().Next(int.MaxValue).ToString();
        //    string appid = "77954376886386987";
        //    //string singanature = (appid + "a90e0a017ff3ac412a32hu18854907gx" + nonce).SHA1();  奇怪，在雅居乐这边SHA1中的+号都被替换为空格了
        //    string singanature = (appid + "a90e0a017ff3ac412a32hu18854907gx" + nonce).MD5Encrypt();
        //    string upUrl = "http://localhost:88/File/UploadOpenBizDoc?appid=" + appid;
        //    upUrl += "&nonce=" + nonce;
        //    upUrl += "&singanature=" + singanature;
        //    upUrl += "&docType=Order-Purchasecontract";
        //    upUrl += "&objid=" + "537ab7a2-c6a0-4ee4-a526-20eb982e26a6";
        //    upUrl += "&objver=" + "V-01";
        //    upUrl += "&fileType=pdf";
        //    upUrl += "&overide=true";

        //    string fn = Server.MapPath("/BizDoc/debug.pdf");
        //    var data = System.IO.File.ReadAllBytes(fn);

        //    Uri upDocUrl = null;
        //    Uri.TryCreate(upUrl, UriKind.Absolute, out upDocUrl);
        //    var rtn = new WebClient().UploadData(upDocUrl, data);
        //}

        [NoSession]
        public JsonResult UploadOpenBizDoc(string appid="",string nonce="",string singanature="",string docType="" ,string objid="",string objver="",string fileType="pdf",bool overide=false)
        {
            FnResult<DyObject> app = API.NoauthorRequest("OpenAppSvc@GetAppInfo").Set("appid", appid).WaitResult();

            if (!app.IsPositive)
            {
                return Json(FnResult.Failure("SoBiz", "IllegalClient"));
            }

            //TODO:检查签名
            string sg = (appid + app.Data["appsec"].ToString() + nonce).MD5Encrypt();

            if (sg != singanature)
            {
                Log("IllegalSinganature:" + appid + ":" + app.Data["appsec"].ToString() + ":" + nonce + ":" + sg + ":" + singanature);
                return Json(FnResult.Failure("SoBiz", "IllegalSinganature"));
            }

            string fname = objid + "？" + docType;
            if (!string.IsNullOrWhiteSpace(objver))
            {
                fname += "？" + objver;
            }
            fname += "." + fileType;
            fname = Url.Encode(fname);  //防止文件名中的特殊字符，会导致查询结果无法返回给页面
            try
            {
                //2016-08-11:改用SHA1签名摘要算法，原MD5太容易碰撞了
                var saveRtn = upload(fname, (fname+appid).SHA1(), "/BizDoc", overide);
                return saveRtn;
            }
            catch(Exception ex)
            {
                Log("文件上传时发生异常"+ex.Message);
                return Json(FnResult.Failure("FileController", "ExceptionOnUpload").SetError("exception", ex.Message));
            }
            finally
            {

            }
        }

        public FileResult GetBizDoc(long objid=-1, long fileId=-1,string savename="")
        {
            var f = API.Request("FileSvc@GetFile")
                       .Set("fileid", fileId)
                       .Set("objid",objid)
                       .WaitResult();

            if (!f.IsPositive)
            {
                return null;
            }
            string fn = f.Data["filename"].ToString();
            string fp = f.Data["path"].ToString();
            string rowPath = Server.MapPath(fp);
            var docExtension = Path.GetExtension(fp);
            
            switch (docExtension)
            {
                case ".pdf":
                case ".doc":
                case ".docx":
                    return File(rowPath, MimeMapping.GetMimeMapping(fn),savename==""?fn:savename);
                default:
                    return null;
            }
        }

        JsonResult upload(string fileName, string token = "",string rootFolder=@"/UserFile",bool overide=false)
        {
            var cntxt = ControllerContext.HttpContext;
            string groupFolder = DateTime.Today.ToString("yyyy-MM-dd");
            string vPath = "/" + groupFolder + "/" + fileName;  //逻辑分组，按日期分组
            string savePath = Server.MapPath(rootFolder + vPath);  //获取文件存储的物理路径
            string dr = Path.GetDirectoryName(savePath);
            if (!Directory.Exists(dr))
            {
                //如果目录不存在，创建目录
                Directory.CreateDirectory(dr);
            }
            if (cntxt.Request.Files.Count > 0)
            {
                #region 直接通过FormData提交的文件对象
                if (string.IsNullOrEmpty(token))
                {
                    //如果客户端未提供md5Token,则在服务端计算
                    token = (cntxt.Request.Files[0].FileName+cntxt.Request.Files[0].ContentLength.ToString()+ cntxt.Request.Files[0].ContentType).SHA1();
                }
                var chk = API.HasFile(token);
                if (chk.IsPositive&&!overide)
                {
                    //已有文件,不需要保存实际文件数据，仅需要返回实际文件的路径用于与业务数据建立关联
                    return Json(chk);
                }

                if (chk.IsPositive && overide)
                {
                    savePath = Server.MapPath(chk.Data["path"].ToString());
                }

                if (System.IO.File.Exists(savePath))
                {
                    System.IO.File.Delete(savePath);
                }
                cntxt.Request.Files[0].SaveAs(savePath);
                var sr = API.SaveFile(Session["uid"] == null ? -1 : (long)Session["uid"], groupFolder, fileName, rootFolder + vPath, cntxt.Request.Files[0].ContentLength, cntxt.Request.Files[0].ContentLength, token,overide);
                return Json(sr);
                #endregion
            }
            else
            {
                #region 以文件流方式上传
                if (string.IsNullOrEmpty(token))
                {
                    //如果客户端未提供md5Token，不允许提交大文件
                    var er = FnResult.Failure("FileAPI", "大文件上传必须在客户端根据文件内容计算唯一的MD5值");
                    return Json(er);
                }
                var chk = API.HasFile(token);
                if (chk.IsPositive && !overide)
                {
                    //已有文件,不需要保存实际文件数据，仅需要返回实际文件的ID和路径用于与业务数据建立关联
                    Log("相同签名的文档:" + fileName + ":" + token);
                    return Json(chk);
                }
                if (chk.IsPositive&&overide)
                {
                    //已有文件，且是覆盖模式，用系统中原文件的物理路径作为本次保存路径
                    savePath =Server.MapPath(chk.Data["path"].ToString());
                }
                if (System.IO.File.Exists(savePath))
                {
                    System.IO.File.Delete(savePath);
                }
                Stream inStream = null;
                FileStream outFile = null;
                try
                {
                    outFile = System.IO.File.OpenWrite(savePath);
                    outFile.Seek(0, SeekOrigin.Begin);

                    inStream = cntxt.Request.InputStream;
                    byte[] tmp = new byte[inStream.Length];
                    inStream.Read(tmp, 0, tmp.Length);
                    int ln = tmp.Length;
                    outFile.Write(tmp, 0, tmp.Length);
                    outFile.Flush();

                    var sr = API.SaveFile(Session["uid"]==null?-1:(long)Session["uid"], groupFolder, fileName, rootFolder + vPath, ln, ln, token,overide);
                    return Json(sr);
                }
                catch (Exception ex)
                {
                    var er = FnResult.Failure("FileAPI", "保存文件时发生错误:" + ex.Message);
                    Log(er.FastJson());
                    return Json(er);
                }
                finally
                {
                    if (inStream != null)
                    {
                        inStream.Dispose();
                    }

                    if (outFile != null)
                    {
                        outFile.Dispose();
                    }
                }

                #endregion
            }
        }

        JsonResult append(string fileName, string token = "", int start = 0, int total = 0,string rootFolder="/UserFile")
        {
            var cntxt = ControllerContext.HttpContext;
            string groupFolder = DateTime.Today.ToString("yyyy-MM-dd");
            if (cntxt.Request.Files.Count == 0)
            {
                var er = FnResult.Failure("FileAPI", "NoFileData").SetError("Error", "Append file must contain file data in FormData");
                return Json(er);
            }
            FnResult<DyObject> chk = null;
            if (Session[token] != null)
            {
                chk = Session[token] as FnResult<DyObject>;
            }
            else
            {
                chk = API.HasFile(token);
            }
            if (!chk.IsPositive)
            {
                //还没有记录
                string vPath = "/" + groupFolder + "/" + fileName;
                string savePath = Server.MapPath(rootFolder + vPath + ".sofile");  //文件存储的物理路径,加后缀表示未完成的部分文件
                string dr = Path.GetDirectoryName(savePath);
                if (!Directory.Exists(dr))
                {
                    //如果目录不存在，创建目录
                    Directory.CreateDirectory(dr);
                }
                if (!System.IO.File.Exists(savePath))
                {
                    cntxt.Request.Files[0].SaveAs(savePath);
                }
                var sr = API.SaveFile(Session["uid"] == null ? -1 : (long)Session["uid"], groupFolder, fileName, rootFolder + vPath, total, cntxt.Request.Files[0].ContentLength, token,true);
                Session.Remove(token);
                Session.Add(token, sr);
                return Json(sr);
            }
            else
            {
                //已有上传的文件
                int tl = int.Parse(chk.Data["length"].ToString());
                int cmpltd = int.Parse(chk.Data["completed"].ToString());
                if (cmpltd == tl)
                {
                    return Json(chk);
                }
                else
                {
                    string vPath = chk.Data["path"].ToString();
                    string rPath = Server.MapPath(vPath + ".sofile");
                    var outFile = System.IO.File.OpenWrite(rPath);
                    var inStream = cntxt.Request.Files[0].InputStream;
                    if (start > outFile.Length)
                    {
                        var er = FnResult.Failure("FileAPI", "BadArgs").SetError("Error", "续传起始位置不能大于已经上传的部分");
                        return Json(er);
                    }
                    try
                    {
                        outFile.Seek(0, SeekOrigin.End);
                        byte[] tmp = new byte[1024];
                        int readLength = inStream.Read(tmp, 0, 1024);
                        int thisLoaded = readLength;
                        while (readLength > 0)
                        {
                            outFile.Write(tmp, 0, readLength);
                            readLength = inStream.Read(tmp, 0, 1024);
                            thisLoaded += readLength;
                        }
                        outFile.Flush();
                        
                        if (thisLoaded == tl - cmpltd)
                        {
                            outFile.Dispose();
                            //本次之后已上传完毕，修改物理文件的路径
                            System.IO.File.Move(rPath, rPath.Remove(rPath.Length - 6, 6));
                        }
                        var sr = API.UpdateFile(Session["uid"] == null ? -1 : (long)Session["uid"], token, cmpltd + thisLoaded);
                        Session.Remove(token);
                        Session.Add(token, sr);
                        return Json(sr);
                    }
                    catch (Exception ex)
                    {
                        var er = FnResult.Failure("FileAPI", "保存文件时发生错误:" + ex.Message);
                        return Json(er);
                    }
                    finally
                    {
                        inStream.Dispose();
                        outFile.Dispose();
                    }
                }

            }
        }

        JsonResult getInfo(string fileName, string md5Token)
        {
            var chk = API.HasFile(md5Token);
            return Json(chk);
        }

        JsonResult getFromWx(string fileName, string token = "", string rootFolder = @"/UserFile", bool overide = false)
        {
            var cntxt = ControllerContext.HttpContext;
            Stream inStream = null;
            FileStream outFile = null;
            try
            {
                inStream = TLB2BController.GetWxMedia(ref fileName); //此方法同时会根据微信的返回信息修改文件名
                if (inStream != null && Path.HasExtension(fileName))
                {
                    string groupFolder = DateTime.Today.ToString("yyyy-MM-dd");
                    string vPath = "/" + groupFolder + "/" + fileName;  //逻辑分组，按日期分组
                    string savePath = Server.MapPath(rootFolder + vPath);  //获取文件存储的物理路径
                    string dr = Path.GetDirectoryName(savePath);
                    if (!Directory.Exists(dr))
                    {
                        //如果目录不存在，创建目录
                        Directory.CreateDirectory(dr);
                    }
                    #region 非追加模式提交的完整文件流
                    if (string.IsNullOrEmpty(token))
                    {
                        //如果客户端未提供Token，不允许提交大文件
                        var er = FnResult.Failure("FileAPI", "大文件上传必须在客户端根据文件内容计算唯一的MD5值");
                        Log("指定从微信获取文件时未提供token" + fileName);
                        return Json(er);
                    }
                    var chk = API.HasFile(token);
                    if (chk.IsPositive && !overide)
                    {
                        //已有文件,不需要保存实际文件数据，仅需要返回实际文件的ID和路径用于与业务数据建立关联
                        return Json(chk);
                    }

                    outFile = System.IO.File.OpenWrite(savePath);
                    outFile.Seek(0, SeekOrigin.Begin);
                    byte[] tmp = new byte[1024];
                    int readLength = inStream.Read(tmp, 0, 1024);
                    while (readLength > 0)
                    {
                        outFile.Write(tmp, 0, readLength);
                        readLength = inStream.Read(tmp, 0, 1024);
                    }
                    //注意：微信返回的ResponseStream不支持查找，因此不能获取Length属性
                    //byte[] tmp = new byte[inStream.Length];
                    //inStream.Read(tmp, 0, tmp.Length);

                    outFile.Flush();
                    int ln = (int)outFile.Length;  //TODO:基于假设保存的文件不会超过int值的最大范围
                    var sr = API.SaveFile(Session["uid"] == null ? -1 : (long)Session["uid"], groupFolder, fileName, rootFolder + vPath, ln, ln, token,false);
                    return Json(sr);
                }
                else
                {
                    Log("微信未返回指定mediaid的文件数据"+fileName+Request.RawUrl);
                    return Json(FnResult.Failure("FileController", "NoResponseFromWx"));
                }
            }
            catch (Exception ex)
            {
                var er = FnResult.Failure("FileController", "保存文件时发生错误").SetError("exception",ex.Message).SetError("RawUrl",Request.RawUrl);
                Log("从微信下载文件出错" + ex.Message);
                return Json(er);
            }
            finally
            {
                if (inStream != null)
                {
                    inStream.Dispose();
                }

                if (outFile != null)
                {
                    outFile.Dispose();
                }
            }

            #endregion
        }
    }
}